package cnisetup

import (
	"context"
	"encoding/json"
	"fmt"
	"strings"
	"time"

	"github.com/f5devcentral/f5-bigip-rest-go/utils"
	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
)

func (ki *CiliumContext) Setup() error {
	cnictx := &ki.CNIContext

	for i := range ki.Kube.Vteps {
		if mac, err := cnictx.searchTunnelMacWith(ki.Kube.Vteps[i].Endpoint); err != nil {
			return err
		} else {
			ki.Kube.Vteps[i].mac = mac
		}
	}

	cm, err := ki.searchConfig()
	if err != nil {
		return err
	}
	eps, cidrs, masks, macs := []string{}, []string{}, []string{}, []string{}
	cm.Data["enable-vtep"] = "false"
	for _, vtep := range ki.Kube.Vteps {
		eps = append(eps, vtep.Endpoint)
		cidrs = append(cidrs, vtep.CIDR)
		masks = append(masks, vtep.Mask)
		macs = append(macs, vtep.mac)
	}

	if len(ki.Kube.Vteps) > 0 {
		cm.Data["enable-vtep"] = "true"
		cm.Data["vtep-endpoint"] = strings.Join(eps, " ")
		cm.Data["vtep-cidr"] = strings.Join(cidrs, " ")
		cm.Data["vtep-mask"] = strings.Join(masks, " ")
		cm.Data["vtep-mac"] = strings.Join(macs, " ")
	}

	b, _ := json.Marshal(map[string]interface{}{"data": cm.Data})
	k8sclient := newKubeClient(ki.Kube.Config)
	if _, err := k8sclient.CoreV1().ConfigMaps(cm.GetNamespace()).Patch(
		cnictx, cm.GetName(), types.StrategicMergePatchType, b, metav1.PatchOptions{}); err != nil {
		return fmt.Errorf("failed to update cilium configmap: %s", err.Error())
	}

	patchBody := fmt.Sprintf(`{"spec":{"template":{"metadata":{"annotations":{"kubectl.kubernetes.io/restartedAt":"%s"}}}}}`, time.Now())
	if _, err := k8sclient.AppsV1().DaemonSets(cm.GetNamespace()).Patch(
		cnictx, "cilium", types.StrategicMergePatchType, []byte(patchBody),
		metav1.PatchOptions{}); err != nil {
		return fmt.Errorf("failed to rollout restart daemonset.apps/cilium: %s", err.Error())
	}

	return nil
}

func (cc *CiliumContext) Teardown() error {
	// TODO: add cilium teardown logic here if need.
	return nil
}

func (cc *CiliumContext) searchConfig() (*v1.ConfigMap, error) {
	k8sclient := newKubeClient(cc.Kube.Config)
	slog := utils.LogFromContext(cc)

	namespaces := []string{"default", "kube-system"}
	name := "cilium-config"

	for _, ns := range namespaces {
		cms, err := k8sclient.CoreV1().ConfigMaps(ns).List(context.TODO(), metav1.ListOptions{})
		if err != nil {
			return nil, err
		}
		for _, cm := range cms.Items {
			if cm.GetName() == name {
				slog.Debugf("found '%s' configmap from %s", name, ns)
				return cm.DeepCopy(), nil
			}
		}
	}
	return nil, fmt.Errorf("not found configmap '%s' from namespaces %s", name, namespaces)
}

func (cc *CiliumContext) nodeConfigs() (map[string]interface{}, error) {
	slog := utils.LogFromContext(cc)
	clientset := newKubeClient(cc.Kube.Config)

	nodeList, err := clientset.CoreV1().Nodes().List(cc, metav1.ListOptions{})
	if err != nil {
		slog.Errorf("failed to list nodes: %s", err.Error())
		return nil, err
	}

	cfgs := map[string]interface{}{}
	nIpToMacV4, _ := allNodesIP2Macs(cc, nodeList)
	for _, tunnel := range cc.Tunnels {
		fcfgs := parseFdbsFrom(tunnel.Name, nIpToMacV4)
		for k, v := range fcfgs {
			cfgs[k] = v
		}
	}
	return cfgs, nil
}
